package org.openwebtop.sunshine.subversion;

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;

import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.collections.MapUtils;
import org.apache.commons.io.IOUtils;
import org.apache.commons.io.output.ByteArrayOutputStream;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang.math.NumberUtils;
import org.openwebtop.sunshine.subversion.model.DiffResult;
import org.openwebtop.sunshine.subversion.model.FileInfo;
import org.openwebtop.sunshine.subversion.model.FileType;
import org.openwebtop.sunshine.subversion.model.RevisionInfo;
import org.tmatesoft.svn.core.ISVNLogEntryHandler;
import org.tmatesoft.svn.core.SVNDepth;
import org.tmatesoft.svn.core.SVNException;
import org.tmatesoft.svn.core.SVNLogEntry;
import org.tmatesoft.svn.core.SVNLogEntryPath;
import org.tmatesoft.svn.core.SVNNodeKind;
import org.tmatesoft.svn.core.SVNProperties;
import org.tmatesoft.svn.core.SVNProperty;
import org.tmatesoft.svn.core.SVNURL;
import org.tmatesoft.svn.core.io.SVNRepository;
import org.tmatesoft.svn.core.wc.SVNDiffClient;
import org.tmatesoft.svn.core.wc.SVNRevision;

public class SubversionClientImpl implements SubversionClient {
	private final static long BASE_REVISION = 0;
	private final static long LASTEST_REVISION = -1;
	private SVNRepository repository;
	private SVNDiffClient diffClient;
	private SVNURL repositoryRoot;

	public SubversionClientImpl(SVNRepository repository, String encoding) throws Exception {
		this.repository = repository;
		this.diffClient = new SVNDiffClient(repository.getAuthenticationManager(), null);
		diffClient.getDiffGenerator().setEncoding(encoding);

		this.repositoryRoot = repository.getRepositoryRoot(false);
	}

	@Override
	public FileType getFileType(String filepath, long revision) throws Exception {
		final SVNNodeKind kind = repository.checkPath(filepath, revision);

		if (kind == SVNNodeKind.DIR) {
			return FileType.DIRECTORY;
		} else if (kind == SVNNodeKind.FILE) {
			return FileType.FILE;
		} else if (kind == SVNNodeKind.NONE) {
			return FileType.NONE;
		}

		return FileType.UNKNOWN;
	}

	@Override
	public DiffResult getDiffInfo(String filepath, long startRevision, long endRevision, String encoding) throws Exception {
		if (StringUtils.isNotBlank(filepath) && StringUtils.isNotBlank(encoding)) {
			final SVNURL absoluteUrl = SVNURL.parseURIEncoded(repository.getLocation().toDecodedString() + filepath);
			ByteArrayOutputStream out = null;

			try {
				out = new ByteArrayOutputStream();
				diffClient.doDiff(absoluteUrl, SVNRevision.create(startRevision), absoluteUrl, SVNRevision.create(endRevision), SVNDepth.INFINITY, true, out);

				final DiffResult diffResult = new DiffResult();
				diffResult.setFilepath(filepath);
				diffResult.setCurrentRivision(startRevision);
				diffResult.setPreviousRevision(endRevision);
				diffResult.setDiffContents(out.toString(encoding));

				return diffResult;
			} finally {
				IOUtils.closeQuietly(out);
			}
		}

		return null;
	}

	@Override
	public FileInfo getFileContents(String filepath, long revision, String encoding) throws Exception {
		if (StringUtils.isNotBlank(filepath) && StringUtils.isNotBlank(encoding)) {
			ByteArrayOutputStream out = null;

			try {
				out = new ByteArrayOutputStream();

				final SVNProperties properties = new SVNProperties();
				repository.getFile(filepath, revision, properties, out);

				final FileInfo fileInfo = new FileInfo();
				fileInfo.setFilepath(filepath);
				fileInfo.setAuthor(properties.getStringValue(SVNProperty.LAST_AUTHOR));
				fileInfo.setRevision(NumberUtils.toLong(properties.getStringValue(SVNProperty.REVISION)));
				fileInfo.setContents(out.toString(encoding));

				return fileInfo;
			} finally {
				IOUtils.closeQuietly(out);
			}
		}

		return null;
	}

	@Override
	public List<RevisionInfo> getRevisionInfoList(long fromRevision, long toRevision, int size) throws Exception {
		final List<RevisionInfo> revisionInfoList = new ArrayList<RevisionInfo>(size);

		final ISVNLogEntryHandler logEntryHandler = new ISVNLogEntryHandler() {

			@Override
			@SuppressWarnings("unchecked")
			public void handleLogEntry(SVNLogEntry svnLogEntry) throws SVNException {
				final RevisionInfo revisionInfo = new RevisionInfo();
				revisionInfo.setRevision(svnLogEntry.getRevision());
				revisionInfo.setDate(svnLogEntry.getDate());
				revisionInfo.setAuthor(svnLogEntry.getAuthor());
				revisionInfo.setCommitMessage(svnLogEntry.getMessage());

				final Map<String, SVNLogEntryPath> changedPathsMap = svnLogEntry.getChangedPaths();

				if (MapUtils.isNotEmpty(changedPathsMap)) {
					final Map<String, Character> map = new HashMap<String, Character>();

					for (Entry<String, SVNLogEntryPath> entry : changedPathsMap.entrySet()) {
						final SVNLogEntryPath logEntryPath = entry.getValue();
						final String absolutePath = repositoryRoot + repository.getRepositoryPath(logEntryPath.getPath());
						final String relativePath = StringUtils.removeStart(absolutePath, repository.getLocation().toDecodedString() + "/");

						map.put(relativePath, logEntryPath.getType());
					}

					revisionInfo.setChangedPathsMap(map);
				}

				revisionInfoList.add(revisionInfo);
			}

		};

		repository.log(null, fromRevision, toRevision, true, false, size, logEntryHandler);

		return revisionInfoList;
	}

	@Override
	public List<RevisionInfo> getRevisionInfoListToBase(long revision, int size) throws Exception {
		return getRevisionInfoList(revision, BASE_REVISION, size);
	}

	@Override
	public List<RevisionInfo> getRevisionInfoListFromLast(long revision, int size) throws Exception {
		final List<RevisionInfo> revisionInfoList = getRevisionInfoList(LASTEST_REVISION, revision, size);

		return revisionInfoList;
	}

	@SuppressWarnings("unchecked")
	public RevisionInfo getRevisionInfo(long revision) throws Exception {
		final SVNLogEntry[] nullSvnLogEntry = new SVNLogEntry[0];

		final Collection<SVNLogEntry> collection = repository.log(null, null, revision, revision, true, true);
		final SVNLogEntry[] svnLogEntries = collection.toArray(nullSvnLogEntry);

		if (svnLogEntries != null && svnLogEntries.length == 1) {
			final SVNLogEntry svnLogEntry = svnLogEntries[0];

			final RevisionInfo revisionInfo = new RevisionInfo();
			revisionInfo.setRevision(svnLogEntry.getRevision());
			revisionInfo.setDate(svnLogEntry.getDate());
			revisionInfo.setAuthor(svnLogEntry.getAuthor());
			revisionInfo.setCommitMessage(svnLogEntry.getMessage());

			final Map<String, SVNLogEntryPath> changedPathsMap = svnLogEntry.getChangedPaths();

			if (MapUtils.isNotEmpty(changedPathsMap)) {
				final Map<String, Character> map = new HashMap<String, Character>();

				for (Entry<String, SVNLogEntryPath> entry : changedPathsMap.entrySet()) {
					final SVNLogEntryPath logEntryPath = entry.getValue();
					final String absolutePath = repositoryRoot + repository.getRepositoryPath(logEntryPath.getPath());
					final String relativePath = StringUtils.removeStart(absolutePath, repository.getLocation().toDecodedString() + "/");

					map.put(relativePath, logEntryPath.getType());
				}

				revisionInfo.setChangedPathsMap(map);
			}

			return revisionInfo;
		}

		return null;
	}

	@Override
	public long getLastestRevision() throws Exception {
		final List<RevisionInfo> revisionInfoList = getRevisionInfoList(LASTEST_REVISION, BASE_REVISION, 1);

		if (CollectionUtils.isNotEmpty(revisionInfoList)) {
			final RevisionInfo revisionInfo = revisionInfoList.get(0);

			return revisionInfo.getRevision();
		}

		return LASTEST_REVISION;
	}

}
